#define _XOPEN_SOURCE
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "convert.h"

// i32 hash_list_compare(const void *a, const void *b) {
//     u64 ix_a = *(u64 *) a;
//     u64 ix_b = *(u64 *) b;

//     if(hl->ha[ix_b] > hl->ha[ix_a]) {
//         return 1;
//     } else {
//         return -1;
//     }
// }

u64 hash_sb(String_Builder *sb) {
    u64 hash = 5381;
    for(u64 i=0; i<sb->length; ++i) {
        hash = hash*33 + sb->items[i];
    }
    return hash;
}

void hash_sba(String_Builder_Array *sba_full, String_Builder_Array *sba_new, HashArray *ha) {
    for(u64 i=0; i<sba_full->length; ++i) {
        u64 hash = hash_sb(&sba_full->items[i]);
        bool new = true;
        for(u64 j=0; j<ha->length; ++j) {
            if(ha->items[j] == hash) {
                new = false;
                break;
            }
        }
        if (new) {
            dyn_array_append(ha, hash);
            sba_append(sba_new, &sba_full->items[i]);
        }
    }
}

void init_hash_list(HashList *hl, String_Builder_Array *sba_full, String_Builder_Array *sba_new, HashArray *ha) {
    hl->length = 0;
    sba_new->length = 0;
    ha->length = 0;
    hash_sba(sba_full, sba_new, ha);
    assert(ha->length == sba_new->length && "Error: arrays must have the same length!\n");
    hl->sba = sba_new;
    hl->ha = ha;
    for(u64 i=0; i<ha->length; ++i) {
        dyn_array_append(hl, i);
    }
}

// MUST HAVE: sba entries must be cstr! (end in '\0')
void sba_to_f32(String_Builder_Array *sba, F32Column *col, u64 header_size) {
    f32 val = 0;
    char *end;
    for(u64 i=header_size; i<sba->length; ++i) {
        String_Builder *sb = &sba->items[i];
        val = strtof(sb->items, &end);
        if(*end != '\0') {
            // TODO: handle this case!
            // we had an invalid value to convert
            fprintf(stderr, "ERROR: Could not convert value %s to float!\n", sb->items);
            val = -3.1415926535;
        }
        dyn_array_append(col, val);
    }
    assert((sba->length-header_size) == col->length && "Error: Converted array has different size than input!");
}

// MUST HAVE: sba entries must be cstr! (end in '\0')
void sba_to_f64(String_Builder_Array *sba, F64Column *col, u64 header_size) {
    f64 val = 0;
    char *end;
    for(u64 i=header_size; i<sba->length; ++i) {
        String_Builder *sb = &sba->items[i];
        val = strtod(sb->items, &end);
        if(*end != '\0') {
            // TODO: handle this case!
            // we had an invalid value to convert
            fprintf(stderr, "ERROR: Could not convert value %s to double!\n", sb->items);
            val = -3.1415926535;
        }
        dyn_array_append(col, val);
    }
    assert((sba->length-header_size) == col->length && "Error: Converted array has different size than input!");
}

// MUST HAVE: sba entries must be cstr! (end in '\0')
void sba_to_str(String_Builder_Array *sba, StrColumn *col, u64 header_size) {
    for(u64 i=header_size; i<sba->length; ++i) {
        sba_append_cstr(col, sba->items[i].items);
    }
    assert((sba->length-header_size) == col->length && "Error: Converted array has different size than input!");
}

// MUST HAVE: sba entries must be cstr! (end in '\0')
void sba_to_categorical(String_Builder_Array *sba, CategoricalColumn *col, u64 header_size) {
    String_Builder_Array headerless_sba = {0};
    StrColumn str_col = {0};
    
    HashList *hl;
    hl = calloc(1, sizeof(*hl));
    assert(hl != NULL && "Could not allocate memory for hash list.");
    
    HashArray *ha;
    ha = calloc(1, sizeof(*ha));
    assert(ha != NULL && "Could not allocate memory for hash array.");
    
    String_Builder_Array *sb_hash;
    sb_hash = calloc(1, sizeof(*sb_hash));
    assert(sb_hash != NULL && "Could not allocate memory for hash string builder array.");

    sba_to_str(sba, &str_col, header_size);
    headerless_sba.capacity = str_col.capacity;
    headerless_sba.length = str_col.length;
    headerless_sba.items = str_col.items;
    
    init_hash_list(hl, &headerless_sba, sb_hash, ha);
    for(u64 i=header_size; i<sba->length; ++i) {
        u64 hash = hash_sb(&sba->items[i]);
        u64 idx;
        for (idx=0; idx<hl->length; ++idx) {
            if(hl->ha->items[idx] == hash) break;
        }
        dyn_array_append(col, idx);
    }
    sba_free(headerless_sba);
    col->hl = hl;
    assert((sba->length-header_size) == col->length && "Error: Converted array has different size than input!");
}

// MUST HAVE: col->format and col->display must be c-strings!
void sba_to_date(String_Builder_Array *sba, DateColumn *col, u64 header_size) {
    Date val = {0};
    for(u64 i=header_size; i<sba->length; ++i) {
        String_Builder *sb = &sba->items[i];
        strptime(sb->items, col->format.items, &val);
        dyn_array_append(col, val);
    }
    assert((sba->length-header_size) == col->length && "Error: Converted array has different size than input!");
}

void print_col_f32(F32Column *col) {
    printf("F32 column:\n");
    for (u64 i=0; i<col->length; ++i) {
        printf("  %.5e\n", col->items[i]);
    }
}

void print_col_f64(F64Column *col) {
    printf("F64 column:\n");
    for (u64 i=0; i<col->length; ++i) {
        printf("  %.5e\n", col->items[i]);
    }
}

void print_col_str(StrColumn *col) {
    printf("String column:\n");
    for (u64 i=0; i<col->length; ++i) {
        printf("  %s\n", col->items[i].items);
    }
}

void print_col_cat(CategoricalColumn *col) {
    printf("Categorical column:\n");
    for (u64 i=0; i<col->length; ++i) {
        u64 idx = col->items[i];
        printf("  (%"PRIx64") - %s \n",
            col->hl->ha->items[idx],
            col->hl->sba->items[idx].items
        );
    }
}

void print_col_date(DateColumn *col) {
    printf("Date column:\n");
    char print_str[256];
    for (u64 i=0; i<col->length; ++i) {
        i32 result = strftime(
            print_str,
            sizeof(print_str),
            col->display.items,
            &col->items[i]
        );
        if (result == 0) {
            fprintf(stderr, "Could not convert date via strftime!\n");
        } else {
            printf("  %s\n", print_str);
        }
    }
}

void free_col_cat(CategoricalColumn *col) {
    sba_free(*col->hl->sba);
    dyn_array_free(*col->hl->ha);
    dyn_array_free(*col->hl);
    dyn_array_free(*col);
}

void free_col_date(DateColumn *col) {
    sb_free(col->format);
    sb_free(col->display);
    dyn_array_free(*col);
}
